"""
数据验证器
=========

提供全面的数据质量检查和验证功能。
"""

import numpy as np
from typing import Dict, List, Tuple, Optional, Any
import warnings


class DataValidator:
    """
    数据验证器类
    
    负责检查输入数据的质量、完整性和一致性。
    """
    
    def __init__(self):
        """初始化数据验证器"""
        self.validation_rules = {
            'price_fields': ['open', 'high', 'low', 'close', 'vwap'],
            'volume_fields': ['volume', 'adv20'],
            'derived_fields': ['returns']
        }
    
    def validate_all(self, data: Dict[str, np.ndarray], 
                    strict: bool = False) -> Dict[str, Any]:
        """
        执行全面的数据验证
        
        Parameters:
        -----------
        data : Dict[str, np.ndarray]
            输入数据
        strict : bool
            是否使用严格模式（严格模式下任何错误都会抛出异常）
            
        Returns:
        --------
        Dict[str, Any]
            验证结果报告
        """
        report = {
            'is_valid': True,
            'errors': [],
            'warnings': [],
            'checks': {}
        }
        
        # 基础格式检查
        format_result = self.check_ohlcv_format(data)
        report['checks']['format'] = format_result
        if not format_result['is_valid']:
            report['is_valid'] = False
            report['errors'].extend(format_result['errors'])
        
        # 时间序列检查
        timeseries_result = self.validate_time_series(data)
        report['checks']['timeseries'] = timeseries_result
        if not timeseries_result['is_valid']:
            report['is_valid'] = False
            report['errors'].extend(timeseries_result['errors'])
        
        # 缺失值检查
        missing_result = self.check_missing_values(data)
        report['checks']['missing'] = missing_result
        report['warnings'].extend(missing_result['warnings'])
        
        # 价格逻辑检查
        price_result = self.validate_price_logic(data)
        report['checks']['price_logic'] = price_result
        if not price_result['is_valid']:
            if strict:
                report['is_valid'] = False
                report['errors'].extend(price_result['errors'])
            else:
                report['warnings'].extend(price_result['errors'])
        
        # 异常值检查
        outlier_result = self.detect_outliers(data)
        report['checks']['outliers'] = outlier_result
        report['warnings'].extend(outlier_result['warnings'])
        
        # 数据一致性检查
        consistency_result = self.check_data_consistency(data)
        report['checks']['consistency'] = consistency_result
        if not consistency_result['is_valid']:
            if strict:
                report['is_valid'] = False
                report['errors'].extend(consistency_result['errors'])
            else:
                report['warnings'].extend(consistency_result['errors'])
        
        return report
    
    def check_ohlcv_format(self, data: Dict[str, np.ndarray]) -> Dict[str, Any]:
        """
        检查OHLCV数据格式
        
        Parameters:
        -----------
        data : Dict[str, np.ndarray]
            输入数据
            
        Returns:
        --------
        Dict[str, Any]
            格式检查结果
        """
        result = {
            'is_valid': True,
            'errors': [],
            'details': {}
        }
        
        required_fields = ['open', 'high', 'low', 'close', 'volume']
        
        # 检查必需字段
        missing_fields = [field for field in required_fields if field not in data]
        if missing_fields:
            result['is_valid'] = False
            result['errors'].append(f"Missing required fields: {missing_fields}")
        
        # 检查数据类型和形状
        for field, values in data.items():
            field_result = {'type': str(type(values)), 'shape': None, 'dtype': None}
            
            if not isinstance(values, np.ndarray):
                result['is_valid'] = False
                result['errors'].append(f"Field {field} is not numpy array")
                field_result['error'] = 'Not numpy array'
            else:
                field_result['shape'] = values.shape
                field_result['dtype'] = str(values.dtype)
                
                if values.ndim != 2:
                    result['is_valid'] = False
                    result['errors'].append(f"Field {field} must be 2D array")
                    field_result['error'] = 'Wrong dimensions'
                
                if not np.issubdtype(values.dtype, np.number):
                    result['is_valid'] = False
                    result['errors'].append(f"Field {field} must be numeric")
                    field_result['error'] = 'Non-numeric data'
            
            result['details'][field] = field_result
        
        # 检查形状一致性
        if data:
            shapes = [values.shape for values in data.values() if isinstance(values, np.ndarray)]
            if shapes and not all(shape == shapes[0] for shape in shapes):
                result['is_valid'] = False
                result['errors'].append("All fields must have the same shape")
        
        return result
    
    def validate_time_series(self, data: Dict[str, np.ndarray]) -> Dict[str, Any]:
        """
        验证时间序列数据
        
        Parameters:
        -----------
        data : Dict[str, np.ndarray]
            输入数据
            
        Returns:
        --------
        Dict[str, Any]
            时间序列验证结果
        """
        result = {
            'is_valid': True,
            'errors': [],
            'details': {}
        }
        
        if not data:
            result['is_valid'] = False
            result['errors'].append("No data provided")
            return result
        
        # 获取数据维度
        first_field = next(iter(data.values()))
        if not isinstance(first_field, np.ndarray) or first_field.ndim != 2:
            result['is_valid'] = False
            result['errors'].append("Invalid data format")
            return result
        
        n_stocks, n_days = first_field.shape
        result['details']['n_stocks'] = n_stocks
        result['details']['n_days'] = n_days
        
        # 检查最小时间长度
        min_days = 20  # 最少需要20个交易日
        if n_days < min_days:
            result['is_valid'] = False
            result['errors'].append(f"Insufficient time series length: {n_days} < {min_days}")
        
        # 检查股票数量
        if n_stocks < 1:
            result['is_valid'] = False
            result['errors'].append("No stocks in dataset")
        
        return result
    
    def check_missing_values(self, data: Dict[str, np.ndarray]) -> Dict[str, Any]:
        """
        检查缺失值
        
        Parameters:
        -----------
        data : Dict[str, np.ndarray]
            输入数据
            
        Returns:
        --------
        Dict[str, Any]
            缺失值检查结果
        """
        result = {
            'warnings': [],
            'details': {}
        }
        
        for field, values in data.items():
            if isinstance(values, np.ndarray):
                total_elements = values.size
                missing_count = np.sum(np.isnan(values))
                missing_ratio = missing_count / total_elements if total_elements > 0 else 0
                
                result['details'][field] = {
                    'total_elements': total_elements,
                    'missing_count': missing_count,
                    'missing_ratio': missing_ratio
                }
                
                if missing_ratio > 0.1:  # 超过10%缺失
                    result['warnings'].append(
                        f"Field {field} has high missing ratio: {missing_ratio:.2%}"
                    )
                elif missing_ratio > 0.05:  # 超过5%缺失
                    result['warnings'].append(
                        f"Field {field} has moderate missing ratio: {missing_ratio:.2%}"
                    )
        
        return result
    
    def validate_price_logic(self, data: Dict[str, np.ndarray]) -> Dict[str, Any]:
        """
        验证价格逻辑关系
        
        Parameters:
        -----------
        data : Dict[str, np.ndarray]
            输入数据
            
        Returns:
        --------
        Dict[str, Any]
            价格逻辑验证结果
        """
        result = {
            'is_valid': True,
            'errors': [],
            'details': {}
        }
        
        price_fields = ['open', 'high', 'low', 'close']
        available_fields = [field for field in price_fields if field in data]
        
        if len(available_fields) < 4:
            return result  # 如果没有完整的OHLC数据，跳过检查
        
        open_price = data['open']
        high = data['high']
        low = data['low']
        close = data['close']
        
        # 检查 high >= max(open, close) 和 low <= min(open, close)
        high_valid = np.logical_and(high >= np.maximum(open_price, close),
                                   high >= np.minimum(open_price, close))
        low_valid = np.logical_and(low <= np.maximum(open_price, close),
                                  low <= np.minimum(open_price, close))
        
        # 统计违反逻辑的比例
        high_violations = np.sum(~high_valid & ~np.isnan(high_valid))
        low_violations = np.sum(~low_valid & ~np.isnan(low_valid))
        total_valid = np.sum(~np.isnan(high_valid))
        
        if total_valid > 0:
            high_violation_ratio = high_violations / total_valid
            low_violation_ratio = low_violations / total_valid
            
            result['details']['high_violations'] = {
                'count': high_violations,
                'ratio': high_violation_ratio
            }
            result['details']['low_violations'] = {
                'count': low_violations,
                'ratio': low_violation_ratio
            }
            
            if high_violation_ratio > 0.01:  # 超过1%违反
                result['is_valid'] = False
                result['errors'].append(
                    f"High price logic violations: {high_violation_ratio:.2%}"
                )
            
            if low_violation_ratio > 0.01:  # 超过1%违反
                result['is_valid'] = False
                result['errors'].append(
                    f"Low price logic violations: {low_violation_ratio:.2%}"
                )
        
        # 检查价格是否为正数
        for field in available_fields:
            negative_count = np.sum(data[field] <= 0)
            if negative_count > 0:
                result['is_valid'] = False
                result['errors'].append(f"Field {field} contains non-positive values: {negative_count}")
        
        return result
    
    def detect_outliers(self, data: Dict[str, np.ndarray], 
                       method: str = 'iqr') -> Dict[str, Any]:
        """
        检测异常值
        
        Parameters:
        -----------
        data : Dict[str, np.ndarray]
            输入数据
        method : str
            异常值检测方法 ('iqr', 'zscore')
            
        Returns:
        --------
        Dict[str, Any]
            异常值检测结果
        """
        result = {
            'warnings': [],
            'details': {}
        }
        
        for field, values in data.items():
            if isinstance(values, np.ndarray):
                field_result = self._detect_field_outliers(values, method)
                result['details'][field] = field_result
                
                if field_result['outlier_ratio'] > 0.05:  # 超过5%异常值
                    result['warnings'].append(
                        f"Field {field} has high outlier ratio: {field_result['outlier_ratio']:.2%}"
                    )
        
        return result
    
    def _detect_field_outliers(self, values: np.ndarray, method: str) -> Dict[str, Any]:
        """检测单个字段的异常值"""
        valid_values = values[~np.isnan(values)]
        
        if len(valid_values) == 0:
            return {'outlier_count': 0, 'outlier_ratio': 0.0, 'method': method, 'bounds': None}
        
        lower_bound = None
        upper_bound = None
        
        if method == 'iqr':
            q1 = np.percentile(valid_values, 25)
            q3 = np.percentile(valid_values, 75)
            iqr = q3 - q1
            lower_bound = q1 - 1.5 * iqr
            upper_bound = q3 + 1.5 * iqr
            outliers = (valid_values < lower_bound) | (valid_values > upper_bound)
        elif method == 'zscore':
            mean_val = np.mean(valid_values)
            std_val = np.std(valid_values)
            z_scores = np.abs((valid_values - mean_val) / std_val) if std_val > 0 else np.zeros_like(valid_values)
            outliers = z_scores > 3
        else:
            raise ValueError(f"Unknown outlier detection method: {method}")
        
        outlier_count = np.sum(outliers)
        outlier_ratio = outlier_count / len(valid_values)
        
        return {
            'outlier_count': outlier_count,
            'outlier_ratio': outlier_ratio,
            'method': method,
            'bounds': (lower_bound, upper_bound) if method == 'iqr' else None
        }
    
    def check_data_consistency(self, data: Dict[str, np.ndarray]) -> Dict[str, Any]:
        """
        检查数据一致性
        
        Parameters:
        -----------
        data : Dict[str, np.ndarray]
            输入数据
            
        Returns:
        --------
        Dict[str, Any]
            一致性检查结果
        """
        result = {
            'is_valid': True,
            'errors': [],
            'details': {}
        }
        
        # 检查价格数据的合理性
        if 'close' in data:
            close = data['close']
            
            # 检查价格变化的合理性（单日涨跌幅不超过50%）
            if close.shape[1] > 1:
                returns = np.diff(close, axis=1) / close[:, :-1]
                extreme_returns = np.abs(returns) > 0.5
                extreme_count = np.sum(extreme_returns)
                
                result['details']['extreme_returns'] = {
                    'count': extreme_count,
                    'ratio': extreme_count / returns.size if returns.size > 0 else 0
                }
                
                if extreme_count > returns.size * 0.001:  # 超过0.1%的极端收益率
                    result['is_valid'] = False
                    result['errors'].append(f"Too many extreme returns: {extreme_count}")
        
        # 检查成交量的合理性
        if 'volume' in data:
            volume = data['volume']
            
            # 检查成交量是否为非负数
            negative_volume = np.sum(volume < 0)
            if negative_volume > 0:
                result['is_valid'] = False
                result['errors'].append(f"Negative volume values: {negative_volume}")
            
            # 检查成交量的变化
            if volume.shape[1] > 1:
                volume_changes = np.diff(volume, axis=1)
                zero_volume_days = np.sum(volume == 0)
                
                result['details']['volume_analysis'] = {
                    'zero_volume_days': zero_volume_days,
                    'zero_volume_ratio': zero_volume_days / volume.size if volume.size > 0 else 0
                }
        
        return result
    
    def validate_data_shape(self, data: Dict[str, np.ndarray], 
                           expected_shape: Optional[Tuple[int, int]] = None) -> Dict[str, Any]:
        """
        验证数据形状
        
        Parameters:
        -----------
        data : Dict[str, np.ndarray]
            输入数据
        expected_shape : Optional[Tuple[int, int]]
            期望的数据形状
            
        Returns:
        --------
        Dict[str, Any]
            形状验证结果
        """
        result = {
            'is_valid': True,
            'errors': [],
            'details': {}
        }
        
        if not data:
            result['is_valid'] = False
            result['errors'].append("No data provided")
            return result
        
        shapes = {}
        for field, values in data.items():
            if isinstance(values, np.ndarray):
                shapes[field] = values.shape
            else:
                result['is_valid'] = False
                result['errors'].append(f"Field {field} is not numpy array")
        
        result['details']['shapes'] = shapes
        
        # 检查所有字段形状是否一致
        unique_shapes = list(set(shapes.values()))
        if len(unique_shapes) > 1:
            result['is_valid'] = False
            result['errors'].append(f"Inconsistent shapes: {shapes}")
        
        # 检查是否符合期望形状
        if expected_shape and unique_shapes:
            actual_shape = unique_shapes[0]
            if actual_shape != expected_shape:
                result['is_valid'] = False
                result['errors'].append(
                    f"Shape mismatch: expected {expected_shape}, got {actual_shape}"
                )
        
        return result